/*
 * perceptron_main.cpp
 *
 *  Created on: Feb 28, 2011
 *      Author: tqlong
 */

#include <iostream>
#include <boost/program_options.hpp>
#include <armadillo>

class ArmaVec : public arma::vec
{
public:
  ArmaVec(int d = 1) : arma::vec(d) {}
  ArmaVec& operator=(const ArmaVec&v) { arma::vec::operator=(v); return *this; }
  double get(int i) const { return at(i); }
  double& ref(int i) { return at(i); }
};

#include "containers/sparse_vec.h"
#include "data/dataset_tpl.h"
#include "learning_tpl.h"

namespace po = boost::program_options;
po::options_description desc("Allowed options");
po::variables_map vm;

void process_options(int argc, char** argv);

template <typename vec>
void generateData()
{
  int n = vm["n"].as<int>();
  int d = vm["d"].as<int>();
  Dataset<vec> data;
  data.generate(n, d);
  data.save(vm["data"].as<std::string>());
}

template <typename vec>
void onlineLearn()
{
  std::vector<double> params;
  params.push_back(vm["alpha"].as<double>());

  Dataset<vec> data(vm["data"].as<std::string>());
  LearningAlgorithm<vec> *p = NULL;
  std::vector<Perceptron<vec> > experts;

  if (vm["method"].as<std::string>() == "perceptron") {
    p = new Perceptron<vec>(data.dim());
    p->setParameters(params);
  }
  else if (vm["method"].as<std::string>() == "wm") {
    WeightedMajority<vec> *wm = new WeightedMajority<vec>;
    std::vector<double> wm_params;
    wm_params.push_back(vm["beta"].as<double>());
    wm->setParameters(wm_params);
    int ne = vm["ne"].as<int>();
    for (int i = 0; i < ne; i++) {
      experts.push_back(Perceptron<vec>(data.dim()));
      experts.back().setParameters(params);
    }
    for (int i = 0; i < ne; i++)
      wm->add(experts[i]);
    p = wm;
  }
  std::cout << "start = \n"; p->print(); std::cout << "\n";

  double total_error;
  int maxiter = vm["iter"].as<int>();
  for (int iter = 0; iter < maxiter; iter++) {
    total_error = 0;
    while (data.next()) {
      double pred = p->predict(data.x());
      double error = p->update(data.x(), data.y(), pred);
      total_error += fabs(error);
    }
    std::cout << "iter " << iter << " total error = " << total_error << "\n";
    if (total_error < 1e-10) break;
    if (iter < maxiter-1) data.restart();
  }
  std::cout << "result = \n"; p->print(); std::cout << "\n";
}
//typedef arma::sparse_vec vec;
//typedef arma::vec vec;

int main(int argc, char** argv) {
  process_options(argc, argv);
  if (vm.count("help") || !vm.count("data")) {
    std::cout << desc << "\n";
    return 0;
  }
  if (vm.count("gen")) {
    if (vm.count("sparse"))
      generateData<arma::sparse_vec>();
    else
      generateData<ArmaVec>();
    return 0;
  }


  if (vm.count("sparse"))
    onlineLearn<arma::sparse_vec>();
  else
    onlineLearn<ArmaVec>();

  return 0;
}

void process_options(int argc, char** argv)
{
  desc.add_options()
    ("help", "produce this usage")
    ("data", po::value<std::string>(), "file contains training samples")
    ("sparse", "use sparse vector representation")
    ("gen", "generate random data")
    ("n", po::value<int>()->default_value(10), "number of points")
    ("d", po::value<int>()->default_value(2), "input dimension")
    ("iter", po::value<int>()->default_value(10), "maximum number of iterations")
    ("alpha", po::value<double>()->default_value(1.0), "learning rate")
    ("ne", po::value<int>()->default_value(2), "number of experts")
    ("beta", po::value<double>()->default_value(0.5), "expert learning rate")
    ("method", po::value<std::string>()->default_value("perceptron"), "learning algorithm: perceptron, wm")
    ;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
}
